/*
 * usb_utility.h
 *
 *  Created on: 08.02.2009
 *      Author: fregat
 */

#ifndef USB_UTILITY_H_
#define USB_UTILITY_H_


# include <iostream>
# include <sstream>
# include <usb.h>
# include <boost/shared_ptr.hpp>
# include <boost/bind.hpp>
# include <boost/function.hpp>
# include <list>
# include <vector>


namespace fregat {
namespace microchip {
namespace usb {

typedef struct usb_device				usb_device_t;
typedef struct usb_bus					usb_bus_t;
typedef struct usb_config_descriptor	usb_config_descriptor_t;
typedef unsigned short					vendor_t;
typedef unsigned short					productid_t;
typedef unsigned char					endpointaddr_t;
typedef unsigned						timeoutms_t;
typedef int								usb_device_descriptor_t;

class deviceCollector;
class configCollector;
class interfaceCollector;
class endpointCollector;
class device;
class endpoint;

class endpoint_bulk;
class inpoint_bulk;
class outpoint_bulk;

enum  endpoint_type_t {
	control=USB_ENDPOINT_TYPE_CONTROL,
	isochron=USB_ENDPOINT_TYPE_ISOCHRONOUS,
	bulk=USB_ENDPOINT_TYPE_BULK,
	interrupt=USB_ENDPOINT_TYPE_INTERRUPT
};


class bus : public usb_bus_t {
	deviceCollector *pdc;
private :
	bus ()
	{
	}
public :
	bus (usb_bus_t b);

	void readConfiguration ();

	device *findDevice (vendor_t, productid_t);

	inline usb_bus_t *getNext ()
	{
		return next;
	}

};

class device : public usb_device {
	configCollector 				*pcc;
	endpointCollector 				*pec;
	usb_dev_handle					*pdh;
	fregat::microchip::usb::bus		*pbus;
private :
	device ()
	{
	}

public :
	device (usb_device_t d, fregat::microchip::usb::bus *);

	void readConfiguration ();

	void open ();

	inline bool compare_vendor (vendor_t v, productid_t p)
	{
		return descriptor.idVendor == v &&
			descriptor.idProduct == p;
	}

	usb_dev_handle *getHandle ()
	{
		return pdh;
	}

	void write (const std::vector<unsigned char> &v, endpoint *ep, timeoutms_t to);

	void read (std::vector <unsigned char> &v, endpoint *ep, timeoutms_t to);

	inpoint_bulk 		*findInBulk ();
	outpoint_bulk 		*findOutBulk ();
};

class config : public usb_config_descriptor_t {
	interfaceCollector	*pic;
	device				*pdevice;
private :
	config ()
	{
	}
public :
	config (usb_config_descriptor_t c, device *);

	void readConfiguration (endpointCollector &);

	inline device *getDevice ()
	{
		return pdevice;
	}

};

class interface_ : public usb_interface_descriptor {
	endpointCollector	*pec;
	config				*pconfig;
private :
	interface_ ()
	{
	}
public :
	interface_ (usb_interface_descriptor uid, config *);

	void readConfiguration (endpointCollector &);

	inline config *getConfig ()
	{
		return pconfig;
	}

};

class endpoint_visitor {
public :
	endpoint_visitor ()
	{
	}

	virtual void register_endpoint ()
	{
	}

	virtual void endpoint_visited (endpoint *)
	{
	}
	virtual void endpoint_bulk_visited (endpoint_bulk *)
	{
	}
	virtual void inpoint_bulk_visited (inpoint_bulk *)
	{
	}
	virtual void outpoint_bulk_visited (outpoint_bulk *)
	{
	}
};

class endpoint_bulk_visitor : public endpoint_visitor {
public :
	endpoint_bulk_visitor ()
	{
	}

	void endpoint_bulk_visited (endpoint_bulk *pepb)
	{
	}
};

class inpoint_bulk_search_visitor  : public endpoint_visitor{
	std::list <inpoint_bulk *> ipbl;
public :
	inpoint_bulk_search_visitor ()
	{
	}

	virtual ~inpoint_bulk_search_visitor () {}

	inline void inpoint_bulk_visited (inpoint_bulk *pipb)
	{
		ipbl.push_back(pipb);
	}

	inline std::list <inpoint_bulk *>::iterator begin ()
	{
		return ipbl.begin();
	}

	inline std::list <inpoint_bulk *>::iterator end ()
	{
		return ipbl.end ();
	}

	inline size_t	size ()
	{
		return ipbl.size();
	}
};

class outpoint_bulk_search_visitor  : public endpoint_visitor{
	std::list <outpoint_bulk *> opbl;
public :
	outpoint_bulk_search_visitor ()
	{
	}

	virtual ~outpoint_bulk_search_visitor () {}

	inline void outpoint_bulk_visited (outpoint_bulk *pepb)
	{
		opbl.push_back (pepb);
	}

	inline std::list <outpoint_bulk *>::iterator begin ()
	{
		return opbl.begin();
	}

	inline std::list <outpoint_bulk *>::iterator end ()
	{
		return opbl.end ();
	}

	inline size_t	size ()
	{
		return opbl.size();
	}
};


class endpoint : public usb_endpoint_descriptor {
	interface_			*pinterface;
	endpoint ()
	{
	}
public :
	endpoint (usb_endpoint_descriptor ued, interface_ *);

	inline bool compare_eptype (fregat::microchip::usb::endpoint_type_t et)
	{
		return (USB_ENDPOINT_TYPE_MASK & bmAttributes) == et;
	}

	inline virtual void accept (endpoint_visitor *pepv)
	{
		pepv->endpoint_visited (this);
	}

	inline interface_ *getInterface ()
	{
		return pinterface;
	}
};

class endpoint_bulk : public endpoint {
public :
	endpoint_bulk (usb_endpoint_descriptor uebd, interface_ *pi) : endpoint (uebd, pi)
	{
	}

	inline void accept (endpoint_visitor *pepv)
	{
		pepv->endpoint_bulk_visited (this);
	}
};

class inpoint_bulk : public endpoint_bulk {
public :
	inpoint_bulk (usb_endpoint_descriptor ued, interface_ *pi) : endpoint_bulk (ued, pi)
	{
	}

	void	read (std::vector<unsigned char> &, timeoutms_t = 0);

	inline void accept (endpoint_visitor *pepv)
	{
		pepv->inpoint_bulk_visited (this);
	}

};

class outpoint_bulk : public endpoint_bulk {
public :
	outpoint_bulk (usb_endpoint_descriptor ued, interface_ *pi) : endpoint_bulk (ued, pi)
	{
	}

	void	write (const std::vector <unsigned char> &, timeoutms_t = 0);

	inline void accept (endpoint_visitor *pepv)
	{
		pepv->outpoint_bulk_visited (this);
	}
};


class busCollector {
public :
	typedef boost::function <void (bus *)> iterate_f;
private :
	std::list <bus *>			bus_collector;
public :
	busCollector ()
	{
	}
	inline void 	add (bus *pb)
	{
		bus_collector.push_back (pb);
	}

	inline void		iterateWith (iterate_f ifunc)
	{
		std::for_each (bus_collector.begin(), bus_collector.end(), ifunc);
	}
};

class deviceCollector {
	std::list <device *>			dev_collector;
	bus								*pbus;

public :
	deviceCollector (bus *pb) : pbus (pb)
	{
	}
	inline void 	add (device *pd)
	{
		dev_collector.push_back (pd);
	}
	inline std::list <device *>::iterator begin ()
	{
		return dev_collector.begin ();
	}
	inline std::list <device *>::iterator end ()
	{
		return dev_collector.end ();
	}
};

class configCollector {
	std::list <config *>	conf_collector;
public :
	configCollector ()
	{
	}
	inline void	add (config *pcd)
	{
		conf_collector.push_back (pcd);
	}
};

class interfaceCollector {
	std::list <interface_ *>	interface_collector;
public :
	interfaceCollector ()
	{
	}
	inline void	add (interface_ *pid)
	{
		interface_collector.push_back (pid);
	}

};

class endpointCollector {
private :
	std::list <endpoint *>	endpoint_collector;
public :
	endpointCollector ()
	{
	}
	inline void	add (endpoint *ped)
	{
		endpoint_collector.push_back(ped);
	}

	inline std::list <endpoint *>::iterator begin ()
	{
		return endpoint_collector.begin ();
	}
	inline std::list <endpoint *>::iterator end ()
	{
		return endpoint_collector.end ();
	}

};

class usb_exception : public boost::exception {
protected :
	virtual std::string getString () = 0;
public :
	inline std::string to_string ()
	{
		return getString ();
	}
};

class device_not_found : public usb_exception {
	vendor_t	device_vendor;
	productid_t	product_id;

protected :
	inline std::string getString ()
	{
		std::stringstream ss;
		ss << "Device vendor 0x" << std::hex << device_vendor << "; product_id 0x" << product_id << " not found." << std::endl;
		return ss.str();
	}
public :
	device_not_found (vendor_t v, productid_t pid) : device_vendor (v), product_id (pid)
	{
	}
};

class channel_not_found : public usb_exception {
	usb_device_descriptor_t descr;
protected :
	inline std::string getString ()
	{
		std::stringstream ss;
		ss << __FILE__ << ":" << __LINE__ << ". Channel with usb-descriptor " << descr << " not found." << std::endl;
		return ss.str ();
	}
public :
	channel_not_found (usb_device_descriptor_t d) : descr(d)
	{
	}
};

class to_many_devices : public usb_exception {
	std::list <device *> l;
protected :
	inline std::string getString ()
	{
		std::stringstream ss;
		ss << "Es wurden " << l.size() << " devices gefunden mit gleichem vendor "
		<< std::endl;
		return ss.str ();
	}
public :
	to_many_devices (std::list <device *> l_) : l(l_)
	{
	}
	~to_many_devices () throw () {};
};

class config_invalid : public usb_exception {
protected :
	inline std::string getString ()
	{
		return "Config settings failed";
	}
};

class write_device_failed : public usb_exception {
	std::string err_str;
protected :
	inline std::string getString ()
	{
		std::stringstream ss;
		ss << "ERROR: usb write " << err_str << std::endl;
		return ss.str();
	}
public :
	write_device_failed (int err) : err_str (strerror (-err))
	{
	}

	~write_device_failed () throw () {};
};

class claim_interface_invalid : public usb_exception {
	std::string err_str;
protected :
	inline std::string getString ()
	{
		std::stringstream ss;
		ss << "Cannot claim interface [" << err_str << "]" << std::endl;
		return ss.str ();
	}

public :
	claim_interface_invalid (int err) : err_str (strerror (-err))
	{
	}
	~claim_interface_invalid () throw () {}
};

class invalid : public usb_exception {
protected :
	inline std::string getString () {
		return "Not defined yet!";
	}

};

class device_not_opened : public usb_exception {
	vendor_t v;
	productid_t pid;
protected :
	inline std::string getString () {
		std::stringstream ss;
		ss << "Could not open device 0x" << std::hex << v << " product_id 0x" << pid << std::endl;
		return ss.str ();
	}
public :
	device_not_opened (vendor_t v_, productid_t pid_) : v(v_), pid(pid_)
	{
	}
};

}
}
}


#endif /* USB_UTILITY_H_ */
